﻿#region Using

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Numerics;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

#endregion

namespace FormatPreview {
	public partial class FormatPreviewPanel : UserControl {
		private const string InvariantCultureName = "<Invariant>";
		private const string MSG_InvalidDate = "The value to format is not a valid date and time.";
		private const string MSG_InvalidDTO = "The value to format is not a valid date and time with an offset.";
		private const string MSG_InvalidFloat = "The value to format is not a valid floating point number.";
		private const string MSG_InvalidFormat = "The format string is invalid.";
		private const string MSG_InvalidInteger = "The value to format is not a valid integer number.";

		private string decimalSeparator;
		private string amDesignator, pmDesignator, aDesignator, pDesignator;
		private string pattern;
		// Flags to indicate presence of error information in status bar
		private bool valueInfo;
		private bool formatInfo;

		private string[] numberFormats = {"C", "D", "E", "e", "F", "G", "N", "P", "R", "X", "x"};
		private const int DEFAULTSELECTION = 5;
		private string[] dateFormats = {"g", "d", "D", "f", "F", "g", "G", "M", "O", "R", "s", "t", "T", "u", "U", "Y"};

		public FormatPreviewPanel() {
			InitializeComponent();

			this.Load += this._Load;
			this.OKButton.Click += this.OKButton_Click;
			this.btnClear.Click += btnClear_Click;
			this.FormatStrings.SelectedIndexChanged += this.FormatStrings_SelectedIndexChanged;
			this.DateBox.CheckedChanged += this.DateBox_CheckedChanged;
			this.NumberBox.CheckedChanged += this.NumberBox_CheckedChanged;
			this.Value.TextChanged += this.Value_TextChanged;
			this.CultureNames.SelectedIndexChanged += this.CultureNames_SelectedIndexChanged;
		}

		private void _Load(object sender, EventArgs e) {
			// Disable Value text box.
			OKButton.Enabled = false;

			// Populate CultureNames list box with culture names
//			CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
			CultureInfo[] cultures = new CultureInfo[] { /*CultureInfo.InvariantCulture, */new CultureInfo("ko-KR"), new CultureInfo("en-US") };
			// Define a string array so that we can sort and modify the names.
			List<String> names = new List<String>();
			int currentIndex = 0;// Index of the current culture.

			foreach(var culture in cultures) {
				names.Add(culture.Name);
			}

			names.Sort();
			// Change the name of the invariant culture so it is human readable.
//			names[0] = InvariantCultureName;
			// Add the culture names to the list box.
			this.CultureNames.Items.AddRange(names.ToArray());

			// Make the current culture the selected culture.
			for(int ctr = 0 ; ctr < names.Count ; ctr++) {
				if(names[ctr] == CultureInfo.CurrentCulture.Name) {
					currentIndex = ctr;
					break;
				}
			}
			this.CultureNames.SelectedIndex = currentIndex;

			// Get decimal separator.
			decimalSeparator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;

			// Get am, pm designators.
			amDesignator = DateTimeFormatInfo.CurrentInfo.AMDesignator;
			if(amDesignator.Length >= 1) {
				aDesignator = amDesignator.Substring(0, 1);
			} else {
				aDesignator = String.Empty;
			}

			pmDesignator = DateTimeFormatInfo.CurrentInfo.PMDesignator;
			if(pmDesignator.Length >= 1) {
				pDesignator = pmDesignator.Substring(0, 1);
			} else {
				pDesignator = String.Empty;
			}

			// For regex pattern for date and time components.
			pattern = @"^\s*\S+\s+\S+\s+\S+(\s+\S+)?(?<!" + amDesignator + "|" + aDesignator + "|" + pmDesignator + "|" + pDesignator + @")\s*$";

			Value.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");

			DateBox.Checked = true;
		}

		private void OKButton_Click(object sender, EventArgs e) {
			info.Text = "";
			this.Result.Text = String.Empty;

			string _rtn = "";

			// Get name of the current culture.
			CultureInfo culture = null;
			string cultureName = (string) this.CultureNames.Items[this.CultureNames.SelectedIndex];
			// If the selected culture is the invariant culture, change its name.
			if(cultureName == InvariantCultureName) {
				cultureName = String.Empty;
			}
			culture = CultureInfo.CreateSpecificCulture(cultureName);

			// Parse string as date
			if(this.DateBox.Checked) {
				DateTime dat = DateTime.MinValue;
				DateTimeOffset dto = DateTimeOffset.MinValue;
				long ticks;
				bool hasOffset = false;

				// Is the date a number expressed in ticks?
				if(Int64.TryParse(this.Value.Text, out ticks)) {
					dat = new DateTime(ticks);
				} else {
					// Does the date have three components (date, time offset), or fewer than 3?
					if(Regex.IsMatch(this.Value.Text, pattern, RegexOptions.IgnoreCase)) {
						if(DateTimeOffset.TryParse(this.Value.Text, out dto)) {
							hasOffset = true;
						} else {
							info.Text = MSG_InvalidDTO;
							valueInfo = true;
							return;
						}
					} else {
						// The string is to be interpeted as a DateTime, not a DateTimeOffset.
						if(DateTime.TryParse(this.Value.Text, out dat)) {
							hasOffset = false;
						} else {
							info.Text = MSG_InvalidDate;
							valueInfo = true;
							return;
						}
					}
				}// Format date value.
				if(hasOffset) {
					_rtn = dto.ToString(this.FormatStrings.Text, culture);
				} else {
					_rtn = dat.ToString(this.FormatStrings.Text, culture);
				}
			} else {
				// Handle formatting of a number.
				long intToFormat;
				BigInteger bigintToFormat = BigInteger.Zero;
				double floatToFormat;

				// Format a floating point value.
				if(Value.Text.Contains(decimalSeparator)) {
					try {
						if(!Double.TryParse(Value.Text, out floatToFormat)) {
							info.Text = MSG_InvalidFloat;
						} else {
							_rtn = floatToFormat.ToString(this.FormatStrings.Text, culture);
						}
					} catch(FormatException) {
						info.Text = MSG_InvalidFormat;
						this.formatInfo = true;
					}
				} else {
					// Handle formatting an integer.
					//
					// Determine whether value is out of range of an Int64
					if(!BigInteger.TryParse(Value.Text, out bigintToFormat)) {
						info.Text = MSG_InvalidInteger;
					} else {
						// Format an Int64
						if(bigintToFormat >= Int64.MinValue && bigintToFormat <= Int64.MaxValue) {
							intToFormat = (long) bigintToFormat;
							try {
								_rtn = intToFormat.ToString(this.FormatStrings.Text, culture);
							} catch(FormatException) {
								info.Text = MSG_InvalidFormat;
								this.formatInfo = true;
							}
						} else {
							// Format a BigInteger
							try {
								_rtn = bigintToFormat.ToString(this.FormatStrings.Text, culture);
							} catch(FormatException) {
								info.Text = MSG_InvalidFormat;
								this.formatInfo = true;
							}
						}
					}
				}
			}
			this.Result.Text = _rtn;
			SetHistory();
		}

		void btnClear_Click(object sender, EventArgs e) {
			richTextBox1.Clear();
		}

		private void NumberBox_CheckedChanged(object sender, EventArgs e) {
			if(this.NumberBox.Checked) {
				this.Result.Text = String.Empty;

				this.FormatStrings.Items.Clear();
				this.FormatStrings.Items.AddRange(numberFormats);
				this.FormatStrings.SelectedIndex = DEFAULTSELECTION;
			}
		}

		private void DateBox_CheckedChanged(object sender, EventArgs e) {
			if(this.DateBox.Checked) {
				this.Result.Text = String.Empty;

				this.FormatStrings.Items.Clear();
				this.FormatStrings.Items.AddRange(dateFormats);
				this.FormatStrings.SelectedIndex = DEFAULTSELECTION;
			}
		}

		private void Value_TextChanged(object sender, EventArgs e) {
			this.Result.Text = String.Empty;

			if(valueInfo) {
				info.Text = String.Empty;
				valueInfo = false;
			}
			if(String.IsNullOrEmpty(Value.Text)) {
				OKButton.Enabled = false;
			} else {
				OKButton.Enabled = true;
			}
		}

		private void FormatStrings_SelectedIndexChanged(object sender, EventArgs e) {
			this.Result.Text = String.Empty;
			if(formatInfo) {
				info.Text = String.Empty;
				formatInfo = false;
			}
		}

		private void CultureNames_SelectedIndexChanged(object sender, EventArgs e) { this.Result.Text = String.Empty; }

		void SetHistory() {
			StringBuilder sb = new StringBuilder();
			//int start = (richTextBox1.Text.Length == 0 ? 0 : (richTextBox1.Text.Length - 1));
			int start = richTextBox1.Text.Length;
			sb.Append(Environment.NewLine + "==========================");
			sb.AppendFormat(string.Format("{0}Value : {1}", Environment.NewLine, Value.Text));
			sb.AppendFormat(string.Format("{0}Format : {1}", Environment.NewLine, FormatStrings.Text));
			sb.AppendFormat(string.Format("{0}Culture : {1}", Environment.NewLine, new CultureInfo(CultureNames.Text)));
			sb.AppendFormat(string.Format("{0}Result : {1}", Environment.NewLine, Result.Text));
			sb.AppendLine(Environment.NewLine + "==========================");
			richTextBox1.Text += sb.ToString();
			richTextBox1.SelectionStart = start;
			richTextBox1.ScrollToCaret();
			richTextBox1.Select(start, sb.Length);
		}
	}
}